<?php // $Id: data.php,v 1.16 2006/07/04 13:52:37 alex Exp $

// !!! Исправить архитектуру рекурсии - в нынешнем варианте трудно корректно обрабатывать пустые поля

/**
 * Класс-контейнер вложенных данных
 *
 */
class sharpobj_data_item_data extends sharpobj_data_item  implements Iterator, Countable
{
    // Переопределяем тип данных
    protected $type = 'data';
    // Устанавливаем атрибут рекурсивности
    protected $is_recurse = true;
    // Добавляем атрибут для хранения вложенных элементов
    protected $r_data = null;
    
    protected $cache = null;
	// id объекта
	protected $id=null;
    /**
     * Проверять ли входные данные на скалярность
     */
    protected $check_scalat = true;
    /**
     * Принудительно приводить к нужному типу данных
     */
    protected $set_vartype = false;

       
	function setId($id)
	{
		$this->id = intval($id);
	}
	function getId()
	{
		return intval($this->id);
	}
	function setIdObj(sharpobj_data_interface_item $idobj)
	{
		$this->setId($idobj->get('plain'));
	}
	function getIdObj()
	{
		$idobj = new sharpobj_data_item_int();
		$idobj->set('plain',$this->getId());
		return $idobj;
	}
    function addItem($datapath,$type,$options=null)
    {
        if ($this->pathThis($datapath))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            return false;
        }elseif($this->pathNextLast($datapath))
        {
            // Требуется создать вложенный элемент первого уровня
            return $this->addSubItem($this->pathNextName($datapath),
                                      $type,
                                       $options);
        }else 
        {
            // Требуется создать вложенный элемент более глубокого уровня
            // Проверяем, нужно ли создать контейнер на следующем уровне
            if (!isset($this->r_data[$this->pathNextName($datapath)]))
            {
                // Элемент, в котором мы собираемся создавать вложенный - отсутствует
            	// создаем элемент типа data
            	$this->addItem($this->pathNextName($datapath),'data',$options);
            }
            // Вызываем действие с вложенным элементом
            $this->subitemDo('addItem',$datapath,null,null,array('type'=>$type,'options'=>$options));
        }
    }
    function cloneItem($datapath)
    {
        if ($this->pathThis($datapath))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            // mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            // return false;
            return clone $this;
        }elseif($this->pathNextLast($datapath))
        {
            // Требуется создать копию вложенного элемента
            return $this->cloneSubItem($datapath);
        }else 
        {
            // Требуется создать вложенный элемент более глубокого уровня
            // Вызываем действие с вложенным элементом
            return $this->subitemDo('cloneItem',$datapath);
        }
    }
    function delItem($datapath)
    {
        if ($this->pathThis($datapath))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            return false;
        }elseif($this->pathNextLast($datapath))
        {
            // Требуется удалить вложенный элемент первого уровня
            return $this->delSubItem($datapath);
        }else 
        {
            // Требуется удалить вложенный элемент второго уровня
            // Вызываем действие с вложенным элементом
            return $this->subitemDo('delItem',$datapath);
        }
    }
    /**
     * Добавить вложенный элемент
     *
     * @param unknown_type $type
     * @param unknown_type $options
     * @return unknown
     */
    protected function addSubItem($name,$type_or_dataobj,$options=null)
    {
        // Получаем имя вложенного элемента
        $item_name = $this->pathNextName($name);
        // Получаем остаток пути для вложенного элемента
        $item_path = $this->pathNextPath($name);
        // Получаем ссылку на вложенный элемент следующего уровня
        // (эта переменная использовалась только при проверке существования)
        // $item_link = $this->r_data[$item_name];
        
        /*
        // Теперь поддерживаются безымянные элементы
        if ($this->pathThis($name))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            return false;
        }
        */
        if (isset($this->r_data[$item_name]) 
				AND is_a($this->r_data[$item_name],'sharpobj_data_item'))
        {   // Вызываемый объект не является наследником sharpobj_data_item
            mst_error('Вложенный элемент существует',"Нельзя создать два вложенных элемента с одинаковым именем",1);
            return false;
        }

		if ($type_or_dataobj instanceof sharpobj_data_item)
		{
			// Нам передали готовый объект
			$dataobj = clone $type_or_dataobj;
		}else{
			// Нам передали тип объекта (объект создаем сами)
			$class_name = 'sharpobj_data_item_'.$type_or_dataobj;
			if (class_exists($class_name))
			{	// Класс существует
				$dataobj = new $class_name($options);
			}else
			{
				// Класс sharpobj_data_item_ для данного типа не определен
				mst_error('Невозможно добавить элемент',"Класс '{$class_name}' не определен",1);
				return false;
			}
		}

        // Добавляем вложенный элемент
        if ($this->pathThis($name))
        {
            // Безымянный элемент
            $this->r_data[] = $dataobj;
        }elseif($this->pathNextLast($name))
        {
        	// Именованный элемент
            $this->r_data[$name] = $dataobj;
        }else
        {
			mst_error('Элемент не определен',"Метод ".__METHOD__." "
				."может создавать элементы только первого уровня вложенности",1);
        	return false;
        }
        return true;
    }
    protected function cloneSubItem($name)
    {
        // Получаем имя вложенного элемента
        $item_name = $this->pathNextName($name);
        // Получаем остаток пути для вложенного элемента
        $item_path = $this->pathNextPath($name);
        // Получаем ссылку на вложенный элемент следующего уровня
        $item_link = $this->r_data[$item_name];
        
        if ($this->pathThis($name))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            return false;
        }
        if (!($item_link instanceof sharpobj_data_item))
        {   // Вызываемый объект не является наследником sharpobj_data_item
            mst_error('Вложенный элемент отсутствует',"Вложенный элемент не существует или не правильного типа",1);
            return false;
        }

        return clone $item_link;
    }
    protected function delSubItem($name)
    {
        // Получаем имя вложенного элемента
        $item_name = $this->pathNextName($name);
        // Получаем остаток пути для вложенного элемента
        $item_path = $this->pathNextPath($name);
        // Получаем ссылку на вложенный элемент следующего уровня
        $item_link = $this->r_data[$item_name];
        if ($this->pathThis($name))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",1);
            return false;
        }
        if (!($item_link instanceof sharpobj_data_item))
        {   // Вызываемый объект не является наследником sharpobj_data_item
            mst_error('Вложенный элемент отсутствует',"Вложенный элемент не существует или не правильного типа",1);
            return false;
        }

        unset($this->r_data[$item_name]);

        return true;
    }
    /**
     * Выполнить действие с вложенными элементами
     * должна быть переопределена для класса, поддерживающего вложенность
     *
     * @param string $do
     * @param string $datapath
     * @param string $view
     * @param mixed $data
     */
    protected  function subitemDo($do,$datapath,$view=null,$data=null,$opt=null)
    {
        if ($this->pathThis($datapath))
        {   // Обращение к текущему элементу
            // Ошибка - путь не задан
            mst_error('Имя элемента не задано',"Имя вложенного элемента должно быть задано",2);
            return false;
        }else 
        {
            // Получаем имя вложенного элемента
            $item_name = $this->pathNextName($datapath);
            // Получаем остаток пути для вложенного элемента
            $item_path = $this->pathNextPath($datapath);
            // Получаем ссылку на вложенный элемент следующего уровня
            $item_link = @$this->r_data[$item_name];

            if (!($item_link instanceof sharpobj_data_item))
            {   // Вызываемый объект не является наследником sharpobj_data_item
                if ($do === 'isExists')
                {
                    // Вложенный элемент отсутствует
                    return false;
                }
                mst_error('Вложенный элемент отсутствует',"Вложенный элемент ({$item_name}) не существует или не правильного типа",2);
                return false;
            }
            // Выбираем действие
            switch ($do)
            {
                case 'isExists':
                    return true;
                break;
                case 'addItem':
                    $this->cache->cacheClear('condition');
                    return $item_link->addItem($item_path,
                                        $opt['type'],
                                        $opt['options']);
                break;
                case 'cloneItem':
                    return $item_link->cloneItem($item_path);
                break;
                case 'delItem':
                    $this->cache->cacheClear('condition');
                    return $item_link->delItem($item_path);
                break;
                case 'get':
                    return $item_link->get($view,
                                $item_path,null,$opt);
                break;
                case 'set':
                    $this->cache->cacheClear('condition');
                    return $item_link->set($view,$data,
                                $item_path,$opt);
                break;
                case 'clear':
                    $this->cache->cacheClear('condition');
                    return $item_link->clear($item_path);
                break;
                case 'type':
                    $this->cache->cacheClear('condition');
                    return $item_link->type($item_path);
                break;
                case 'isEmpty':
                    return $item_link->isEmpty($item_path);
                break;
                case 'isNull':
                    return $item_link->isNull($item_path);
                break;
                case 'isNoData':
                    return $item_link->isNoData($item_path);
                break;
                case 'isData':
                    return $item_link->isData($item_path);
                break;
                case 'isDefault':
                    return $item_link->isDefault($item_path);
                break;
                case 'isError':
                    return $item_link->isError($item_path);
                break;
                default:
                    mst_error('Команда не поддерживается',"Команда {$do} не поддерживается",1);
                    return false;
                break;
            }
        }

    }
    function subitemNames()
    {   // Именя всех вложенных элементов первого уровня
        return array_keys($this->r_data);
    }
    protected function getCondition()
    {
        // Получить состояние объекта
        // Для проверки состояний объекта пользователем
        // используйте функции проверки состояний
        // Для внешней проверки используйте функции isXXX
        
        // Ищем значение в кеше
        if ($this->cache->cacheCheck('condition'))
        {
            return $this->cache->cacheGet('condition');
        }
        
        $result = $this->subitemRecurse(null,null);
        // print_r($result);
        
        if ($result['iserror'])
        {   // Есть хоть одна ошибка
            $condition='error';
        }elseif ($result['isempty'])
        {   // Все элементы пусты
            // print_r($result);
            $condition='empty';
        }elseif ($result['isnull'])
        {   // Все элементы null
            $condition='null';
        }elseif ($result['isdefault'])
        {   // Все элементы default
            $condition='default';
        }else
        {
            $condition='data';
        }
        $this->cache->cacheSet('condition',$condition);
        return $condition;
    }
    protected function  subitemRecurse($do,$view,$data=null,$opt=null,$no_empty=false)
    {   // Выполнить действие над всеми элементами, взять данные из массива и вернуть массив
        $result = array();
        // Если хоть одна ошибка - изменится на true
        $result['iserror'] = false;
        // Если хоть один не пустой - изменится на false
        $result['isempty'] = true;
        // Если хоть один не null - изменится на false
        $result['isnull'] = true;
        // Если хоть один не default - изменится на false
        $result['isdefault'] = true;
        // Нужен для накопления ошибок, возвращаемых из set
        $no_false = true;
        if (is_null($data)){$data=array();}
        $items = $this->subitemNames();
        foreach ($items as $name)
        {
            // Проверяем свойства
            // Пустой элеменнт
            if ($this->subitemDo('isEmpty',$name))
            {   // Текущий элемент пуст
                $result['empty'][$name] = true;
            }else
            {   // Хотя бы один из просмотренных элементов не пуст
                $result['isempty'] = false;            	
            }
/*			if (isset($data[$name]) AND $no_empty)
			{
				// Текущий элемент пуст
                $result['empty'][$name] = true;
			}*/
            // Ошибка
            if (!$result['iserror'] AND $this->subitemDo('isError',$name))
            {   // Хотя бы один элемент вернул ошибку
                $result['iserror'] = true;
            }
            // null
            if ($result['isnull'] AND !$this->subitemDo('isNull',$name))
            {   // Хотя бы один элемент вернул ошибку
                $result['isnull'] = false;
            }
            // default
            if ($result['isdefault'] AND !$this->subitemDo('isDefault',$name))
            {   // Хотя бы один элемент вернул ошибку
                $result['isdefault'] = false;
            }
            // Выполняем действие
            if (!is_null($do) AND !($no_empty AND @$result['empty'][$name]))
            {   // Если элемент не пуст или отображение не пустых элементов разрешено
                $result['res'][$name] = $this->subitemDo($do,$name,$view,@$data[$name],$opt);
                if ($result['res'][$name] === false)
                {   // Текущий элемент вернул false
                    $no_false = false;
                }
            }

        }
        $result['no_false']=$no_false;
        return $result;
    }
    /*
    function viewIn_plain()
    {
    }
    function viewOut_plain()
    {
    }
    */


    function __clone()
    {   // Создать независимую копию объекта
        $items = $this->subitemNames();
		$this->cache = new simplecach();
        foreach ($items as $name)
        {
            $this->r_data[$name] = self::cloneSubItem($name);
        }
    }
    function __construct($options=null)
    {
        parent::__construct($options);
        $this->cache = new simplecach();
		$this->r_data = array();
    }
    

    
    // Объявляем фильтры представлений   
    // "простой вид" - данные как есть
    function viewIn_plain($opt=null)
    {
        $result = $this->subitemRecurse('set','plain',$this->getSource(),$opt);
        return $result['no_false'];
    }
    function viewOut_plain($opt=null)
    {
        if (!isset($opt['no_empty']))
        {
            $opt['no_empty'] = true;
        }
        $result = $this->subitemRecurse('get','plain',null,$opt,$opt['no_empty']);
        // print_r($result);
        return $result['res'];
    }
    function viewOut_html($opt=null)
    {
        $result = $this->subitemRecurse('get','html',null,$opt,true);
        // print_r($result);
        return $result['res'];
    }
    function viewIn_form($opt=null)
    {
        $result = $this->subitemRecurse('set','form',$this->getSource(),$opt);
        return $result['no_false'];
    }
    function viewOut_form($opt=null)
    {
        if (!isset($opt['no_empty']))
        {
            $opt['no_empty'] = true;
        }
        $result = $this->subitemRecurse('get','form',null,$opt,$opt['no_empty']);
        // print_r($result);
        return $result['res'];
    }
/*
	// Пока эти представления не нужны - они отключены 
	// (все равно null обрабатывается некорректно)
    // "простой вид" - данные как есть
    function viewIn_form($opt=null)
    {
        $result = $this->subitemRecurse('set','form',$this->getSource(),$opt);
        return $result['no_false'];
    }
    function viewOut_form($opt=null)
    {
        $result = $this->subitemRecurse('get','form',null,$opt,true);
        // print_r($result);
        return $result['res'];
    }
    function viewOut_html($opt=null)
    {
        $result = $this->subitemRecurse('get','html',null,$opt,true);
        // print_r($result);
        return $result['res'];
    }

    function viewIn_sql($opt=null)
    {
        $result = $this->subitemRecurse('set','sql',$this->getSource(),$opt);
        return $result['no_false'];
    }
    function viewOut_sql($opt=null)
    {
        $result = $this->subitemRecurse('get','sql',null,$opt,true);
        // print_r($result);
        return $result['res'];
    }
*/
    function viewOut_validate($opt=null)
    {
        $result = $this->subitemRecurse('get','validate',null,$opt,false);
        // print_r($result);
        return $result['res'];
    }
    
    /**
     * Вернуть данные в виде плоского массива (для mysql_flex)
     * @param array $opt - параметры обработки
     * @return array
     */
    function viewOut_rdata($opt=null)
    {
		$result = array();
		// Представление, в котором выбираются данные
        if (!isset($opt['view'])){$opt['view']='sql';}
        // Добавлять ли в массив информацию о пустых элементах
        if (!isset($opt['noempty'])){$opt['noempty']=true;}
        // Нужны ли значения?
        if (!isset($opt['novalue'])){$opt['novalue']=false;}
    	foreach ($this->r_data as $iname=>$item)
		{
			//
			$rval = $item->get('rdata',null,$opt);
			if (isset($rval['type']))
			{
				// Вложенный элемент не рекурсивен
				if ($opt['noempty'] AND $item->isEmpty())
				{
					// Отсутствующие данные вставлять не нужно
					continue;
				}
				// Добавляем имя элемента
				$rval['name']=htmlspecialchars($iname);
				// Записываем элемент в результат
				$result['res'][] = $rval;
			}else
			{
				// Вложенный элемент  рекурсивен
				foreach($rval as $subrval)
				{
					// Добавляем имя вложенного элемента
					$subrval['name']=htmlspecialchars($iname)."/{$subrval['name']}";
					// Записываем элемент в результат
					$result['res'][] = $subrval;
				}
			}
		}
        return $result['res'];
    }
	function viewIn_rdata($opt=null)
    {
		$result = array('res'=>array());
        if (!isset($opt['view'])){$opt['view']='sql';}
        if (!isset($opt['noempty'])){$opt['noempty']=true;}
        // Рекурсивны ли данные?
        $rdata = $this->getSource();
        if (isset($rdata['type']))
		{
			// Данные не рекурсивны
			$this->addItem($rdata['name'],$rdata['type']);
			$this->set($opt['view'],$rdata['value'],$rdata['name']);
		}else
		{
			// Данные рекурсивны
    		foreach ($rdata as $row)
			{
				//
				$this->addItem($row['name'],$row['type']);
				$this->set($opt['view'],$row['value'],$row['name']);
			}

		}

        return $result['res'];
    }
	
	// Итератор
    // Посчитать элементы
    public function count()
    {
        return count($this->r_data);
    }
    
    // Функции итерации
    public function rewind()
    {
        reset($this->r_data);
    }
    public function current()
    {
        $var = current($this->r_data);
        return $var;
    }

    public function key()
    {
        $var = key($this->r_data);
        return $var;
    }

    public function next()
    {
        $var = next($this->r_data);
        return $var;
    }
    public function valid()
    {
        $var = $this->current() !== false;
        return $var;
    }
}
?>